package classconverstation.cscl.ist.psu.edu.client.classdisplay;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;


import classconverstation.cscl.ist.psu.edu.client.messengers.Course;


import classconverstation.cscl.ist.psu.edu.client.messengers.ClassCommonsMessage;
import classconverstation.cscl.ist.psu.edu.client.services.ClassCommonsService;
import classconverstation.cscl.ist.psu.edu.client.services.ClassCommonsServiceAsync;


import com.allen_sauer.gwt.dnd.client.DragContext;
import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.allen_sauer.gwt.dnd.client.drop.AbsolutePositionDropController;
import com.allen_sauer.gwt.dnd.client.drop.DropController;
import com.allen_sauer.gwt.dnd.client.util.DOMUtil;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

public class ClassDisplay implements EntryPoint {

	Label classLabel;
	FlowPanel tickerPanel;
	Label stackModeLabel;
	Label pinModeLabel;

	private ClassCommonsServiceAsync classCommonsServiceSvc = GWT.create(ClassCommonsService.class);
	AbsolutePanel boundaryPanel = new AbsolutePanel();
	AbsolutePanel targetPanel = new AbsolutePanel();
	PickupDragController dragController;
	ArrayList<ClassCommonsMessage> cm_msgs = new ArrayList<ClassCommonsMessage>();
	// Real time ClassCommons Messages
	ArrayList<ClassCommonsMessage> chat_msgs = new ArrayList<ClassCommonsMessage>();
	// Pin messages
	ArrayList<ClassCommonsMessage> pin_msgs = new ArrayList<ClassCommonsMessage>();

	ArrayList<Pin> pins = new ArrayList<Pin>();
	ArrayList<StackMessage> stacks = new ArrayList<StackMessage>();
	int tickerHeight;
	int cid = -1; 
	static int REFRESH_INTERVAL = 1000 * 5;
	long timestamp = 0;
	long currentTimeStamp = (new Date()).getTime();

	// displayMode 0: pin Mode, 1: stack-Mode
	int displayMode = 0;

	Timer timer = new Timer() {
		public void run() {
			loadCMMsgs(cid, timestamp);
		}
	};

	Timer tickerTimer = new Timer(){
		public void run() {
			int widgetCount = tickerPanel.getWidgetCount();
			if (widgetCount > 0){
				removeExistingTicker();
			}
			showNewTicker();
		}
	};

	public void onModuleLoad() {
		// set uncaught exception handler
		GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
			public void onUncaughtException(Throwable throwable) {
				String text = "Uncaught exception: ";
				while (throwable != null) {
					StackTraceElement[] stackTraceElements = throwable.getStackTrace();
					text += throwable.toString() + "\n";
					for (int i = 0; i < stackTraceElements.length; i++) {
						text += "    at " + stackTraceElements[i] + "\n";
					}
					throwable = throwable.getCause();
					if (throwable != null) {
						text += "Caused by: ";
					}
				}
				DialogBox dialogBox = new DialogBox(true, false);
				DOM.setStyleAttribute(dialogBox.getElement(), "backgroundColor", "#ABCDEF");
				System.err.print(text);
				text = text.replaceAll(" ", "&nbsp;");
				dialogBox.setHTML("<pre>" + text + "</pre>");
				dialogBox.center();
			}
		});
		// use a deferred command so that the handler catches onModuleLoad2() exceptions
		Scheduler.get().scheduleDeferred(new ScheduledCommand() {
			public void execute() {
				onModuleLoad2();
			}
		});
	}
	
	private void onModuleLoad2() {
		// Create a boundary panel to constrain all drag operations
		constructHeader();
		constructContentArea();
		constructTickerArea();
		setLayout();
		startTimer();
	}
	

	private void loadCMMsgs(int course_id, long time){
		if (course_id== -1){
			showChooseClassPopup();

		}
		else{
			// Initialize the service proxy.
			if (classCommonsServiceSvc == null) {
				classCommonsServiceSvc = GWT.create(ClassCommonsService.class);
			}
			// Set up the callback object.
			AsyncCallback<ArrayList<ClassCommonsMessage>> callback = new AsyncCallback<ArrayList<ClassCommonsMessage>>() {
				public void onFailure(Throwable caught) {
					caught.printStackTrace();
				}
				public void onSuccess(ArrayList<ClassCommonsMessage> msgs) {
					ArrayList<ClassCommonsMessage> pin_to_add = new ArrayList<ClassCommonsMessage>();
					ArrayList<ClassCommonsMessage> stack_to_add = new ArrayList<ClassCommonsMessage>();
					for (int i= msgs.size()-1; i >= 0 ; i--){
						ClassCommonsMessage msg = msgs.get(i);
						int type = msg.getType();
						if (type == 3 || type == 4){
							pin_msgs.add(msg);
							pin_to_add.add(msg);
						}
						else{
							if (msg.getPostDate().getTime()>currentTimeStamp)
								chat_msgs.add(msg);
						}
						stack_to_add.add(msg);
						cm_msgs.add(msg);
						timestamp = msg.getPostDate().getTime();
					}
					loadNewPins(pin_to_add);
					loadStackMessages(stack_to_add);
					
					switch (displayMode){
					// pin-mode
					case 0:
						hideStacks();
						break;
					// add pins to the pin list but don't show it on the screen.
					case 1:
						hidePins();
						break;
					default:
						break;
					}
					
				}
			};
			classCommonsServiceSvc.loadClassCommonsMsgs(course_id, timestamp, callback);   
		}
	}
	
	protected void loadStackMessages(ArrayList<ClassCommonsMessage> newStacks) {
		if (newStacks.size() > 0){
			//Window.alert("There are new messages!");
			clearExistingStacksFromInterface();
			addNewStacks(newStacks);
			loadStackMessageOnScreen();
		}
	}
	
	private void clearExistingStacksFromInterface(){
		//Window.alert("Remove the old stack messages!");
		for (int i=0; i< stacks.size(); i++){
			//Window.alert("Remove " + i);
			stacks.get(i).removeFromParent();
		}
	}
	
	protected void addNewStacks(ArrayList<ClassCommonsMessage> newStacks){
		for (int i=0; i< newStacks.size(); i++){
			stacks.add(new StackMessage(newStacks.get(i)));
		}
	}
	
	private void loadStackMessageOnScreen(){
		//Window.alert("Reorganize the stack messages!");
		for (int i=stacks.size()-1; i>=0; i--){
			boundaryPanel.add(stacks.get(i));
		}
		
	}
	
	protected void hideStacks(){
		for (int i = 0; i < stacks.size(); i++) {	
			stacks.get(i).setVisible(false);
		}
	}
		
	
	// load newly posted pins and pin it on the publid display
	protected void loadNewPins(ArrayList<ClassCommonsMessage> newPins) {
		ClassCommonsMessage msg;
		for (int i = 0; i < newPins.size(); i++) {
			msg = newPins.get(i);
			Pin pin = new Pin(msg);
			if (msg.getType() == 4){
				//Window.alert(msg.getContent() +" is solved!");
				pin.setAsSolved();
			}
			boundaryPanel.add(pin);
			pins.add(pin);
			dragController.makeDraggable(pin);
			dragController.setBehaviorDragStartSensitivity(3);

			boundaryPanel.add(pin, 0, 0);

			// determine random label location within target panel
			int left = Random.nextInt(DOMUtil.getClientWidth(boundaryPanel.getElement()) - pin.getOffsetWidth());
			int top = Random.nextInt(DOMUtil.getClientHeight(boundaryPanel.getElement()) - pin.getOffsetHeight());

			// move the label
			boundaryPanel.setWidgetPosition(pin, left, top);

			// make the label draggable
			dragController.makeDraggable(pin);
		}
	}

	protected void clearPins(){
		for (int i=0; i< pins.size(); i++){
			pins.get(i).removeFromParent();	
		}
		pins.clear();
		this.cm_msgs.clear();
		this.chat_msgs.clear();
		this.pin_msgs.clear();

	}

	public void constructHeader(){
		FlowPanel headerPanel = new FlowPanel();
		RootPanel.get("nav").add(headerPanel);
		FlowPanel logoPanel = new FlowPanel();
		logoPanel.addStyleName("sc_logo_top");
		logoPanel.add(this.getLogoTable());
		headerPanel.add(logoPanel);
		FlowPanel linksPanel = new FlowPanel();
		linksPanel.addStyleName("sc_quicklinks_top");
		FlexTable linksTable = this.getLinksTable();
		linksPanel.add(linksTable);
		headerPanel.add(linksPanel);
	}

	public FlexTable getLogoTable(){
		classLabel = new Label("ClassCommons");
		classLabel.addStyleDependentName("sc_logo_font");
		Label choose = new Label ("Choose Another Course");
		choose.addStyleDependentName("margin15");
		choose.addClickHandler(new ClickHandler(){
			@SuppressWarnings("unchecked")
			public void onClick(ClickEvent event) {
				showChooseClassPopup();
			}
		});

		FlexTable fTable = new FlexTable();
		fTable.setWidget(0, 0, classLabel);
		fTable.setWidget(0, 1, choose);

		return fTable;
	}

	public FlexTable getLinksTable(){
		Label displayModeLabel = new Label("Display Mode: ");
		stackModeLabel = new Label("Stack Mode | ");
		//Label slidesModeLabel = new Label("Slides Mode | ");
		pinModeLabel = new Label("Pin Mode ");

		FlexTable fTable = new FlexTable();
		fTable.setWidget(0, 0, displayModeLabel);
		fTable.setWidget(0, 1, stackModeLabel);
		//fTable.setWidget(0, 2, slidesModeLabel);
		fTable.setWidget(0, 2, pinModeLabel);
		setLinkStyles();

		pinModeLabel.addClickHandler(new ClickHandler(){
			public void onClick(ClickEvent event) {
				displayMode = 0;
				setLayout();
				setLinkStyles();
				hideStacks();
				showPins();
			}

		});

		stackModeLabel.addClickHandler(new ClickHandler(){
			public void onClick(ClickEvent event) {
				displayMode = 1;
				setLayout();
				setLinkStyles();
				hidePins();
				showStacks();
			}

		});
		return fTable;

	}
	
	private void hidePins(){
		for (int i=0; i< pins.size(); i++){
			pins.get(i).setVisible(false);	
		}
	}
	
	private void showPins(){
		for (int i=0; i< pins.size(); i++){
			pins.get(i).setVisible(true);	
		}
	}
	
	private void showStacks(){
		for (int i = 0; i< stacks.size(); i++){
			stacks.get(i).setVisible(true);	
		}
	}


	
	private void startTimer(){
		timer.scheduleRepeating(REFRESH_INTERVAL);
		tickerTimer.scheduleRepeating(12*REFRESH_INTERVAL);
	}

	public void constructContentArea(){
	

		// Create a drop target on which we can drop labels
		targetPanel.setPixelSize(300, 200);
		targetPanel.addStyleName("getting-started-blue");

		// Add both panels to the root panel
		RootPanel.get("content_area").add(boundaryPanel);

		//boundaryPanel.add(targetPanel, 10, 10);

		// Create a DragController for each logical area where a set of draggable
		// widgets and drop targets will be allowed to interact with one another.
		//PickupDragController dragController = new PickupDragController(boundaryPanel, true);

		dragController = new PickupDragController(boundaryPanel, false) {
			protected Widget newDragProxy(DragContext context) {
				AbsolutePanel container = new AbsolutePanel();
				DOM.setStyleAttribute(container.getElement(), "overflow", "visible");
				for (Iterator iterator = context.selectedWidgets.iterator(); iterator.hasNext();) {
					Widget widget = (Widget) iterator.next();
					Pin lb = (Pin)widget;
					container.add(lb);
				}
				return container;
			}
		};
		dragController.setBehaviorDragProxy(true);
		// Positioner is always constrained to the boundary panel
		// Use 'true' to also constrain the draggable or drag proxy to the boundary panel
		dragController.setBehaviorConstrainedToBoundaryPanel(false);
		// Allow multiple widgets to be selected at once using CTRL-click
		dragController.setBehaviorMultipleSelection(true);
		// create a DropController for each drop target on which draggable widgets
		// can be dropped
		DropController dropController = new AbsolutePositionDropController(boundaryPanel);
		//DropController dropController = new AbsolutePositionDropController(targetPanel);

		// Don't forget to register each DropController with a DragController
		dragController.registerDropController(dropController);
	}


	public void constructTickerArea(){
		tickerPanel = new FlowPanel();
		tickerHeight = (int) (Window.getClientHeight()*0.1);
		RootPanel.get("chat_area").add(tickerPanel);
	}

	private void removeExistingTicker(){
		tickerPanel.getWidget(0).removeFromParent();
	}

	private void showNewTicker(){
		ClassCommonsMessage ticker;
		if (chat_msgs.size() > 0){
			ticker = chat_msgs.get(0);
			tickerPanel.add(getMarqueePanel(ticker));
			chat_msgs.remove(0);
			if (chat_msgs.size() == 0){
				tickerTimer.scheduleRepeating(20*1000);
			}

		}
		else{
			int height = (int) (Window.getClientHeight()*0.1)-10;
			//HTMLPanel ad = new HTMLPanel("<div style = 'height: "+ (height-5) +"px'; class = 'ticker'>Tom: I think hashtable is very hard, please spend more time on this topic... :) </div>");
			HTMLPanel ad = new HTMLPanel("<div style = 'height: "+ (height-5) +"px'; class = 'ticker'>Post your comments and display it here. </div>");
			tickerPanel.add(ad);
		}
	}

	private HTMLPanel getMarqueePanel(ClassCommonsMessage msg){
		int height = (int) (Window.getClientHeight()*0.1)-10;
		HTMLPanel marquee = new HTMLPanel("<marquee scrollamount='5'  style = 'height: "+ height +"px'; class = 'ticker'>"+msg.getPoster().getFName()+": "+msg.getContent() +"</marquee>");
		return marquee;
	}

	public void showChooseClassPopup(){
		final CourseListPopup pop = new CourseListPopup();
		pop.setGlassEnabled(true);
		pop.setAutoHideEnabled(true);
		pop.center();
		pop.addCloseHandler(new CloseHandler(){
			public void onClose(CloseEvent event) {
				Course selectedCourse = pop.getSelectedCourse();
				if (selectedCourse != null){
					classLabel.setText("ClassCommons - "+selectedCourse.getCourseName());
					cid = selectedCourse.getCourseID();
					clearPins();
					resetTimeStamp();
				}
			}
		});
	}

	public void resetTimeStamp(){
		timestamp = 0;
		currentTimeStamp = (new Date()).getTime();
	}

	public void setLayout(){
		switch (displayMode){
		// Non-Slides Mode
		case 0:
			tickerPanel.setVisible(true);
			RootPanel.get("content_area").addStyleName("85percent");
			RootPanel.get("content_area").removeStyleName("extend");
			RootPanel.get("chat_area").addStyleName("normalTicker");
			RootPanel.get("chat_area").removeStyleName("minimize");
			break;
		// Stack Mode
		case 1:
			tickerPanel.setVisible(false);
			RootPanel.get("content_area").removeStyleName("85percent");
			RootPanel.get("content_area").addStyleName("extend");
			//DOM.getElementById("chat_area").removeFromParent();
			RootPanel.get("chat_area").removeStyleName("normalTicker");
			RootPanel.get("chat_area").addStyleName("minimize");
			break;
		default:
			break;
		}
	}
	
	private void setLinkStyles(){
		int width = Window.getClientWidth();
		int height = Window.getClientHeight();
		switch (displayMode){
		
		// Pin Mode
		case 0:
			boundaryPanel.setPixelSize(width, (int) (0.85*height));
			this.pinModeLabel.addStyleDependentName("linkHighlighted");
			this.pinModeLabel.removeStyleDependentName("linkEnabled");
			this.stackModeLabel.removeStyleDependentName("linkHighlighted");
			this.stackModeLabel.addStyleDependentName("linkEnabled");
			break;
		// Stack Mode
		case 1:	
			boundaryPanel.setPixelSize(width, height-50);
			this.stackModeLabel.addStyleDependentName("linkHighlighted");
			this.stackModeLabel.removeStyleDependentName("linkEnabled");
			this.pinModeLabel.removeStyleDependentName("linkHighlighted");
			this.pinModeLabel.addStyleDependentName("linkEnabled");
			break;
		default:
			break;
		}
	}

}
